package seckill

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/pkg/errors"
	"github.com/streadway/amqp"
	"github.com/zeromicro/go-zero/core/limit"
	"mall/app/seckill/cmd/api/global_key"
	"mall/common/config"
	"mall/common/utils"
	"strconv"
	"time"

	"mall/app/seckill/cmd/api/internal/svc"
	"mall/app/seckill/cmd/api/internal/types"

	"github.com/zeromicro/go-zero/core/logx"
)

const (
	burst = 1000
	rate  = 200
)

type SeckillLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

func NewSeckillLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SeckillLogic {
	return &SeckillLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

// Seckill 秒杀处理逻辑
func (l *SeckillLogic) Seckill(req *types.SeckillReq) (*types.SeckillResp, error) {
	if err := l.limit(); err != nil {
		return nil, err
	}
	var redisTo types.SeckillSkuRedisTo
	nowTime := time.Now().Unix()
	jsonStr, err := l.svcCtx.RedisClient.Hget(global_key.SkukillCacheKey, req.KillId)
	if err != nil || len(jsonStr) == 0 {
		l.Error(err)
		return nil, err
	}
	err = json.Unmarshal([]byte(jsonStr), &redisTo)
	if err != nil {
		l.Error(err)
		return nil, err
	}
	tmp := fmt.Sprintf("%d_%d", redisTo.PromotionSessionId, redisTo.SkuId)
	if tmp != req.KillId || redisTo.RandomCode != req.Key || nowTime > redisTo.EndTime || nowTime < redisTo.StartTime || req.Num > int(redisTo.SeckillLimit) {
		l.Error(err)
		return nil, err
	}
	uid := l.ctx.Value("userId")
	key := fmt.Sprintf("%v_%d_%d", uid, redisTo.PromotionSessionId, redisTo.SkuId)
	value := strconv.Itoa(req.Num)
	ttl := redisTo.EndTime - time.Now().Unix()
	b, err := l.svcCtx.RedisClient.SetnxEx(key, value, int(ttl))
	if err != nil {
		l.Error(err)
		return nil, err
	}
	fmt.Println(b)
	// 未参与过秒杀
	if b {
		result, err := l.svcCtx.RedisClient.Eval(`if tonumber(redis.call('get', KEYS[1]))>0
then
	redis.call('decr', KEYS[1])
	return 1
else
	return 0
end`, []string{global_key.Sku_Stock + redisTo.RandomCode})
		if err != nil {
			l.Error(err)
			return nil, err
		}
		res := result.(int64)
		fmt.Println(res)
		// 秒杀成功
		if res == 1 {
			id := l.svcCtx.IdGenerator.Generate()
			orderTo := types.SeckillOrderTo{}
			orderTo.OrderId = int64(id)
			orderTo.Num = req.Num
			orderTo.PromotionSessionId = redisTo.PromotionSessionId
			orderTo.SkuId = redisTo.SkuId

			orderTo.UserId, err = strconv.ParseInt(fmt.Sprintf("%v", uid), 10, 64)
			// orderTo写入消息队列
			bytes, err := json.Marshal(orderTo)
			fmt.Println(bytes)
			channel, err := utils.GetRabbitMQChannel()
			err = channel.Publish(config.SeckillOrderExchangeName, config.SeckillOrderRoutingKey, false, false, amqp.Publishing{
				ContentType: "text/plain",
				Body:        bytes,
			})
			if err != nil {
				l.Error(err)
				return nil, err
			}
			return &types.SeckillResp{OrderId: orderTo.OrderId}, nil
		}
	}
	return nil, err
}

func (l *SeckillLogic) limit() error {
	limiter := limit.NewTokenLimiter(rate, burst, l.svcCtx.RedisClient, "rate-test")
	if limiter.Allow() {
		return nil
	} else {
		return errors.New("服务忙~")
	}
}
